package cn.edu.lcu.cs.javaprogramming.collection;

import cn.edu.lcu.cs.javaprogramming.BaseTest;
import lombok.extern.java.Log;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 用成绩集合演示Stream操作
 *
 * @author ling
 * @date 2022/4/20 0:04
 */
@Log
public class ScoreStreamDemo extends BaseTest {
    Set<Score> scores = ScoreUtil.getScores();

    /**
     * 根据ID查询第一个满足条件的实例
     *
     * @param id
     */
    @ParameterizedTest
    @ValueSource(longs = {1, -1})
    void findById(Long id) {
        Score found = scores.stream()
                // 根据条件过滤：保留符合条件的元素
                .filter(score -> id.equals(score.getId()))
                // 可能查询到，也可能查不到
                .findFirst()
                // 如果查不到，返回null
                .orElse(null);
        // 如果查不到，也可抛出异常
        //.orElseThrow(NoSuchElementException::new);

        System.out.println("id = " + id);
        System.out.println("found = " + found);
    }

    /**
     * 查询所有及格的成绩，及统计及格的人数
     */
    @Test
    void findPassed() {
        //Stream<Score> passedScores = scores.stream().filter(score -> score.getTotalScore() >= 60);
        //passedScores.forEach(System.out::println);
        long passedCount = scores.stream().filter(score -> score.getTotalScore() >= 60).count();
        System.out.println("passedCount = " + passedCount);
        scores.stream().filter(score -> score.getTotalScore() >= 60).forEach(System.out::println);
    }

    /**
     * 判断是否有总成绩恰好等于特定值的成绩
     *
     * @param totalScore
     */
    @ParameterizedTest
    @ValueSource(doubles = {0, 60, 100})
    void isTotalScoreAnyMatch(Double totalScore) {
        System.out.println("totalScore = " + totalScore);
        // 浮点数不能直接做相等比较，而是判断它们差值的绝对值是否小于一个可接受的范围。
        //boolean anyMatch = scores.stream().anyMatch(score -> totalScore.equals(score.getTotalScore()));
        //如果两个成绩差值的绝对值小于0.5，认为这两个成绩相等
        boolean anyMatch = scores.stream().anyMatch(score -> Math.abs(score.getTotalScore() - totalScore) < 0.5);
        System.out.println("anyMatch = " + anyMatch);
        scores.stream().filter(score -> Math.abs(score.getTotalScore() - totalScore) < 0.5).forEach(System.out::println);
    }

    /**
     * 对总成绩进行统计
     */
    @Test
    void statisticsOfTotalScore() {
        // 边收集，边统计平均值
        Double averageTotalScore = scores.stream().collect(Collectors.averagingDouble(Score::getTotalScore));
        System.out.println("averageTotalScore = " + averageTotalScore);

        // 边收集，边统计各种值，如最大、最小、平均
        DoubleSummaryStatistics statistics = scores.stream()
                .collect(Collectors.summarizingDouble(Score::getTotalScore));
        double max = statistics.getMax();
        double min = statistics.getMin();
        double average = statistics.getAverage();
        long count = statistics.getCount();
        System.out.println("count = " + count);
        System.out.println("max = " + max);
        System.out.println("min = " + min);
        System.out.println("average = " + average);
    }

    /**
     * 查询不及格学生的姓名
     */
    @Test
    void findFailingStudentName() {
        Object[] failingStudents = scores.stream()
                // 过滤，保留符合条件的（及格的），剔除不及格的。
                .filter(score -> score.getTotalScore() < 60)
                // 映射，流的转换，把成绩流转换为字符串流。
                .map(Score::getStudentName)
                // 把流收集为数组
                .toArray();
        System.out.println(Arrays.toString(failingStudents));
    }

    /**
     * 查询不及格学生的姓名及成绩
     */
    @Test
    void findFailingStudentNameAndTotalScore() {
        Object[] objects = scores.stream()
                // 过滤，保留符合条件的（及格的），剔除不及格的。
                .filter(score -> score.getTotalScore() < 60)
                // 映射（流的转换）
                // 原始流：成绩对象的流
                // 目标流：仅包含姓名与总成绩，不值得创建专门的JavaBean用来封装数据，可以用映射接收数据。
                .map(score -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("name", score.getStudentName());
                    map.put("totalScore", score.getTotalScore());
                    return map;
                })
                .toArray();
        System.out.println(Arrays.toString(objects));
    }

    /**
     * 先根据班级递减排序，再根据学号递减排序
     */
    @Test
    void sortByClazzAndStudentNo() {
        scores.stream()
                .sorted(Comparator
                        // 先根据班级名称排序
                        .comparing(Score::getClazzName)
                        // 如果班级名称相同，再按照学号排序
                        .thenComparing(Score::getStudentNo)
                        // 整体顺序颠倒
                        .reversed())
                .forEach(System.out::println);
    }

    /**
     * 收集为映射
     */
    @Test
    void toMap() {
        Map<Long, Score> scoreMap = scores.stream()
                // 收集为映射
                // 第一个参数为key，第二个参数为value
                // k-v均可由表达式计算得到
                // 本例中，value原样返回，没有进一步处理
                .collect(Collectors.toMap(Score::getId, score -> score));
        scoreMap.forEach((k, v) -> System.out.println(k + "\t" + v));
    }

    /**
     * 按照条件，将流分为两个区
     */
    @Test
    void partitioningBy() {
        Map<Boolean, List<Score>> parts = scores.stream()
                //.map(Score::getTotalScore)
                .collect(Collectors.partitioningBy(score -> score.getTotalScore() < 60));
        parts.forEach((k, v) -> System.out.println((k ? "不及格" : "及格") + "\n\t" + v));
    }

    /**
     * 按照某些条件分组
     */
    @Test
    void groupingBy() {
        Map<String, List<Score>> groups = scores.stream()
                // 按照班级名称分组
                .collect(Collectors.groupingBy(score -> score.getClazzName()));
        groups.forEach((k, v) -> System.out.println(k + "\n\t" + v));
    }

    /**
     * 返回逗号分隔的不及格学生姓名
     */
    @Test
    void csvFailingRoster() {
        String failingRoster = scores.stream()
                // 保留不及格的成绩
                .filter(score -> score.getTotalScore() < 60)
                // 成绩流转换为姓名组成的字符串流
                .map(Score::getStudentName)
                // 字符串连接操作，可指定前缀与后缀
                .collect(Collectors.joining(", ", "不及格人员名单\n[", "]"));
        System.out.println(failingRoster);

    }
}
